package hypeerweb;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;

import states.NodeState;

public class Node implements Comparable<Node>, NodeInterface {
	private Connections connections;
	private WebId webid;
	private NodeState state = NodeState.TerminalNodeState;
	/*
	 * private Node fold; private Node surrogatefold; private Node
	 * inversesurrogatefold;
	 * 
	 * 
	 * //When adding a new neighbor, it will always be the largest, so I think
	 * it would be better //to use something like an ArrayList so that we can
	 * access the last/largest element //in constant time. -Leon private
	 * List<Node> neighbors; private List<Node> uppointers; private List<Node>
	 * downpointers;
	 */

	public static final Node NULL_NODE = new Node(){
		public void AddMe(){}
		public void RemoveMe(){}
		public void DeleteMe(){}};

	private Node() {
		connections = new Connections();
		webid = WebId.NULL_WEB_ID;
		/*
		 * neighbors = new ArrayList<Node>(); uppointers = new
		 * ArrayList<Node>(); downpointers = new ArrayList<Node>();
		 * surrogatefold = NULL_NODE; inversesurrogatefold = NULL_NODE; fold =
		 * NULL_NODE;
		 */
	}

	public Node(int id) {
		webid = new WebId(id);
		connections = new Connections(this);
		/*
		 * neighbors = new ArrayList<Node>(); uppointers = new
		 * ArrayList<Node>(); downpointers = new ArrayList<Node>(); fold = this;
		 * surrogatefold = NULL_NODE; inversesurrogatefold = NULL_NODE;
		 */
		if (connections.getFold().getId() == 0)
			setState(NodeState.HypercubeCapState);
	}

	public Node(int id, int height) {
		webid = new WebId(id, height);
		connections = new Connections();

		/*
		 * neighbors = new ArrayList<Node>(); uppointers = new
		 * ArrayList<Node>(); downpointers = new ArrayList<Node>(); fold = this;
		 * surrogatefold = NULL_NODE; inversesurrogatefold = NULL_NODE;
		 */
	}

	public WebId getWebId() {
		return webid;
	}

	public int getId() {
		return webid.getValue();
	}

	public int getHeight() {
		return webid.getHeight();
	}

	public List<Node> getNeighbors() {
		return connections.getNeighbors();
	}

	// to get Surrogate Neighbors
	public List<Node> getDownPointers() {
		return connections.getDownPointers();
	}

	// to get Inverse Surrogate Neighbors
	public List<Node> getUpPointers() {
		return connections.getUpPointers();
	}

	public Connections getConnections() {
		return connections;
	}

	public void setWebId(WebId webId) {
		webid = webId;
	}

	public void setConnections(Connections connect) {
		connections = connect;

		List<Node> neighbors = connections.getNeighbors();

		Iterator<Node> iter = neighbors.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();

			node.addNeighbor(this);
		}

		List<Node> downpointers = connections.getDownPointers();

		iter = downpointers.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();

			node.addUpPointer(this);
		}

		Node fold = connections.getFold();

		if (fold.getId() == 0) {
			setState(NodeState.HypercubeCapState);
		} else {
			setState(NodeState.TerminalNodeState);
		}

		fold.setFold(this);
	}
	
	public void updateConnections(Connections otherConnect, WebId otherId, NodeState otherState)
	{
		connections = otherConnect;
		webid = otherId;
		setState(otherState);
		
		List<Node> neighbors = connections.getNeighbors();

		Iterator<Node> iter = neighbors.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();

			node.updateNeighbor(this);
		}

		List<Node> downpointers = connections.getDownPointers();

		iter = downpointers.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();

			node.updateUpPointer(this);
		}
		
		List<Node> uppointers = connections.getUpPointers();
		
		iter = uppointers.iterator();
		
		while(iter.hasNext())
		{
			Node node = iter.next();
			
			node.updateDownPointer(this);
		}

		Node fold = connections.getFold();
		Node isf = connections.getInverseSurrogateFold();
		Node sf = connections.getSurrogateFold();

		if(fold.compareTo(NULL_NODE) != 0)
		{
			fold.updateFold(this);
		}
		if(isf.compareTo(NULL_NODE) != 0)
		{
			isf.updateSurrogateFold(this);
		}
		if(sf.compareTo(NULL_NODE) != 0)
		{
			sf.updateInverseSurrogateFold(this);
		}
	}

	private void updateDownPointer(Node node) {
		connections.updateDownPointer(node);
	}

	private void updateInverseSurrogateFold(Node node) {
		connections.updateInverseSurrogateFold(node);
	}

	private void updateSurrogateFold(Node node) {
		connections.updateSurrogateFold(node);
		
	}

	private void updateFold(Node node) {
		connections.updateFold(node);
	}

	private void updateUpPointer(Node node) {
		connections.updateUpPointer(node);
		
	}

	private void updateNeighbor(Node node) {
		connections.updateNeighbor(node);
		
	}

	//should become redundant
	public void addNeighbor(Node neighbor) {
		// create a copy of parent node
		int parentID = neighbor.getWebId().getParentsValue();
		Node parent = new Node(parentID);

		connections.removeDownPointer(parent);

		connections.addNeighbor(neighbor, this.webid);

		updateHeight();

		if (connections.getDownPointers().size() > 0) {
			setState(NodeState.DownPointingNodeState);
		} else {
			setState(NodeState.StandardNodeState);
		}
	}

	public void updateHeight() {
		int height = connections.getHeight();

		int id = webid.getHeight();

		for (int i = 0; i < height - id; i++) {
			webid.incrementHeight();
		}
	}

	public void removeNeighbor(Node neighbor) {
		connections.removeNeighbor(neighbor);
	}

	public void addUpPointer(Node upPointer) {
		int parentID = upPointer.getWebId().getParentsValue();
		Node parent = new Node(parentID);

		connections.addUpPointer(upPointer);

		connections.receivedChild(parent);

		setState(NodeState.UpPointingNodeState);
	}

	public void removeUpPointer(Node upPointer) {
		connections.removeUpPointer(upPointer);

		if (connections.getUpPointers().size() == 0) {
			setState(NodeState.StandardNodeState);
		}
	}

	public void addDownPointer(Node downPointer) {
		connections.addDownPointer(downPointer);
	}

	public void removeDownPointer(Node downPointer) {
		connections.removeDownPointer(downPointer);
	}

	//should become redundant
	public void setFold(Node newFold) {
		// move fold to surrogate fold, then set the fold
		Node fold = connections.getFold();

		connections.setInverseSurrogateFold(fold);
		connections.setFold(newFold);

		connections.setSurrogateFold(NULL_NODE);

		if (newFold.getId() == 0) {
			setState(NodeState.HypercubeCapState);
		} else {
			if (getState() == NodeState.HypercubeCapState) {
				if (connections.getUpPointers().size() == 0) {
					setState(NodeState.StandardNodeState);
				} else {
					setState(NodeState.UpPointingNodeState);
				}
			}
		}
	}

	public void setSurrogateFold(Node newSurrogateFold) {
		connections.setSurrogateFold(newSurrogateFold);
		// connections.setFold(NULL_NODE);
	}

	public void setInverseSurrogateFold(Node newInverseSurrogateFold) {
		connections.setInverseSurrogateFold(newInverseSurrogateFold);
	}

	public Node getFold() {
		return connections.getFold();
	}

	public Node getSurrogateFold() {
		return connections.getSurrogateFold();
	}

	public Node getInverseSurrogateFold() {
		return connections.getInverseSurrogateFold();
	}

	public void addChild(Node newNode) {

		int height = webid.getHeight();

		int id = webid.getValue() + (int) Math.pow(2, height);

		Connections childConnect = new Connections(connections, this);

		Node ISF = connections.getInverseSurrogateFold();
		Node F = connections.getFold();

		if (ISF == NULL_NODE) {
			connections.setSurrogateFold(F);
			connections.setFold(NULL_NODE);
		} else {
			connections.setInverseSurrogateFold(NULL_NODE);
		}

		connections.deleteUpPointers();

		newNode.setWebId(new WebId(id));

		newNode.setConnections(childConnect);

	}

	/*
	 * private Node getLargestConnection(){ Node largest; if (fold != null){
	 * largest = fold; }else { largest = surrogatefold; } Node neighbor =
	 * neighbors.get(neighbors.size() - 1); if (neighbor.getId() >
	 * largest.getId()){ largest = neighbor; } neighbor =
	 * uppointers.get(uppointers.size() - 1); if (neighbor.getId() >
	 * largest.getId()){ largest = neighbor; } return largest; }
	 * 
	 * private Node getSmallestDownPointer(){ if (downpointers.size() == 0){
	 * return new Node(); }else { return downpointers.get(0); } }
	 * 
	 * private Node getLargestUpPointer(){ if (uppointers.size() == 0){ return
	 * new Node(); }else { return uppointers.get(uppointers.size() - 1); } }
	 * 
	 * private Node getSmallestChildlessNeighbor(){ //TODO decide how to
	 * implement this function return new Node(); }
	 */

	public int compareTo(Node arg0) {
		Integer zero = this.getId();
		Integer one = arg0.getId();
		return zero.compareTo(one);
	}

	public boolean equals(Object obj) {
		if ((obj.getClass() != this.getClass()))
			return false;

		Node node = (Node) obj;

		if (this.compareTo(node) == 0) {
			return true;
		} else {
			return false;
		}
	}

	public void addToHyPeerWeb(Node node) {

		Node edgeNode = this.findTerminalNode();
		edgeNode.getState().findInsertionAndDeletionPoints(edgeNode, edgeNode, edgeNode.getSmallestSurrNeighbor());
		Node insertionNode = NodeState.getInsertionPoint();
		
		insertionNode.addChild(node);
	}
	
	public void removeFromHyPeerWeb() {

		Node edgeNode = this.findTerminalNode();
		edgeNode.getState().findInsertionAndDeletionPoints(edgeNode, edgeNode, edgeNode.getSmallestSurrNeighbor());
		Node deletionNode = NodeState.getDeletionPoint();
		
		//TODO remvoe the node
	}

	public Node findTerminalNode() {
		return getState().findTerminalNode(this);
	}

	public Node findInsertionPoint() {
		getState().findLastNode(this);
		return NodeState.getInsertionPoint();
	}

	public SimplifiedNodeDomain constructSimplifiedNodeDomain() {
		HashSet<Integer> sidePointers = new HashSet<Integer>();
		HashSet<Integer> upPointers = new HashSet<Integer>();
		HashSet<Integer> downPointers = new HashSet<Integer>();
		List<Node> uppointers = connections.getUpPointers();
		List<Node> neighbors = connections.getNeighbors();
		List<Node> downpointers = connections.getDownPointers();

		Iterator<Node> iter = neighbors.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();
			sidePointers.add(node.getId());
		}

		iter = uppointers.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();
			upPointers.add(node.getId());
		}

		iter = downpointers.iterator();

		while (iter.hasNext()) {
			Node node = iter.next();
			downPointers.add(node.getId());
		}
		Node fold = connections.getFold();
		Node inversesurrogatefold = connections.getInverseSurrogateFold();
		Node surrogatefold = connections.getSurrogateFold();

		int fld = (fold == null) ? -1 : fold.getId();
		int insrrfld = (inversesurrogatefold == null) ? -1
				: inversesurrogatefold.getId();
		int srrfld = (surrogatefold == null) ? -1 : surrogatefold.getId();

		return new SimplifiedNodeDomain(getId(), getHeight(), sidePointers,
				upPointers, downPointers, fld, srrfld, insrrfld, getState().STATE_ID);
	}

	public Node getLargestConnection() {
		return connections.getLargestConnection();
	}

	public Node getSmallestSurrNeighbor() {
		return connections.getSmallestSurrNeighbor();
	}

	public Node getSmallestNeighborWithoutChild() {
		return connections.getSmallestNeighborWithoutChild();
	}

	public Node getLargestISN() {
		return connections.getLargestISN();
	}

	public String toString() {
		return "WebId: " + webid.getValue() + " Height: " + webid.getHeight();
	}

	@Override
	public void AddMe(Node node) {
		System.out.println("Called AddMe on Node");
	}

	@Override
	public void ReplaceMe(Node node) {
		System.out.println("Called ReplaceMe on Node");

	}

	@Override
	public void DeleteMe(Node node) {
		System.out.println("Called DeleteMe on Node");

	}

	public NodeState getState() {
		return state;
	}

	public void setState(NodeState state) {
		this.state = state;
	}

}
